<?php

namespace app\common\library\wechat;


use think\facade\Db;

/**
 * 微信支付
 * Class WxPay
 * @package app\common\library\wechat
 */
class WxPay extends WxBase
{
    // 微信支付配置
    private $config;


    /**
     * 构造函数
     * WxPay constructor.
     * @param $config
     */
    public function __construct($config = false)
    {
        parent::__construct();
        $this->config = $config;
        $this->config !== false && $this->setConfig($this->config['app_id'], $this->config['app_secret']);
    }

    public function notify($type)
    {

        if (!$xml = file_get_contents('php://input')) {
            $this->returnCode(false, 'Not found DATA');
        }
        // 将服务器返回的XML数据转化为数组
        $data = $this->fromXml($xml);
        $order = array();
        if ($type == 1) {
            $order = Db::name('order')->where(['order_on' => $data['out_trade_no']])->find();
        }

        if ($type == 2) {
            $order = Db::name('crad_order')->where(['order_on' => $data['out_trade_no']])->find();
        }
        // 查询当前配置参数
        $info = Db::name('store_shop')->where(['shop_id' => $order['wxapp_id']])->find();
        $wxapp = Db::name('wxapp')->where(['wxapp_id' => $info['wxapp_id']])->find();
        // 设置支付秘钥
        if ($wxapp['app_id'] || $wxapp['mchid'] || $wxapp['apikey']) {
            $this->config['apikey'] = $wxapp['apikey'];
        } else {

            $this->config['apikey'] = '5634HFGJH23FDUfdgdfgeerg5DEBT432';
        }
        // 保存微信服务器返回的签名sign
        $dataSign = $data['sign'];
        // sign不参与签名算法
        unset($data['sign']);
        // 生成签名
        $sign = $this->makeSign($data);
        // 判断签名是否正确 判断支付状态
        if (
            ($sign !== $dataSign)
            || ($data['return_code'] !== 'SUCCESS')
            || ($data['result_code'] !== 'SUCCESS')
        ) {
            $this->returnCode(false, '签名失败');
        }
        // 订单支付成功业务处理
        $status = false;
        if ($type == 1) {
            $updata = array(
                'status' => 1,
                'pay_type' => 1,
                'pay_time' => time(),
            );
            $status = Db::name('order')->where(['order_on' => $data['out_trade_no']])->update($updata);
            //更新用户积分
            if ($order['integral']) {
                Db::name('user')->where(['user_id' => $order['user_id']])->setDec('integral', $order['integral']);
            }
            //更新用户优惠卷
            if ($order['coupon_id']) {
                $upda = array('status' => 1);
                Db::name('user_coupon')->where(['user_id' => $order['user_id'], 'coupon_id' => $order['coupon_id']])->update($upda);
            }
        }
        if ($type == 2) {
            $end_time = 0;
            if ($order['is_type'] == 1) {
                $crad = Db::name('crad')->where(['id' => $order['crad_id']])->find();
                $end_time = time() + intval($crad['order_day']) * 86400;
            }
            $updata = array(
                'status' => 1,
                'pay_type' => 1,
                'end_time' => $end_time,
                'pay_time' => time(),
            );
            $status = Db::name('crad_order')->where(['order_on' => $data['out_trade_no']])->update($updata);
            file_put_contents('./pingCang.txt', "type=2 : out_trade_no" . $data['out_trade_no'] . "更改成功" . "\r\n", FILE_APPEND);
        }
        if ($status == false) {
            $this->returnCode(false, 'fail');
        }
        // 返回状态
        $this->returnCode(true, 'OK');


    }


    /**
     * 申请退款API
     * @param $transaction_id
     * @param double $total_fee 订单总金额
     * @param double $refund_fee 退款金额
     * @return bool
     * @throws BaseException
     */
    public function refundPrice($transaction_id, $total_fee, $refund_fee)
    {
        // 当前时间
        $time = time();
        // 生成随机字符串
        $nonceStr = md5($time . $transaction_id . $total_fee . $refund_fee);
        // API参数

        $params = [
            'appid' => $this->appId,
            'mch_id' => trim($this->config['mchid']),
            'nonce_str' => $nonceStr,
            'out_trade_no' => $transaction_id,
            'out_refund_no' => $time,
            'total_fee' => $total_fee * 100,
            'refund_fee' => $refund_fee * 100,
        ];


        // 生成签名
        $params['sign'] = $this->makeSign($params);

        // 请求API

        $url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
        $result = $this->post($url, $this->toXml($params), true, $this->getCertPem());


        // 请求失败
        if (empty($result)) {
            return false;
        }
        // 格式化返回结果
        $prepay = $this->fromXml($result);

        // 请求失败
        if ($prepay['return_code'] === 'FAIL') {
            return false;
        }
        if ($prepay['result_code'] === 'FAIL') {
            return false;
        }
        return true;
    }

    /**
     * 申请退款API
     * @param $transaction_id
     * @param double $total_fee 订单总金额
     * @param double $refund_fee 退款金额
     * @return bool
     * @throws BaseException
     */
    public function refund($transaction_id, $total_fee, $refund_fee)
    {
        // 当前时间
        $time = time();
        // 生成随机字符串
        $nonceStr = md5($time . $transaction_id . $total_fee . $refund_fee);
        // API参数

        $params = [
            'appid' => $this->appId,
            'mch_id' => trim($this->config['mchid']),
            'nonce_str' => $nonceStr,
            'out_trade_no' => $transaction_id,
            'out_refund_no' => $time,
            'total_fee' => $total_fee * 100,
            'refund_fee' => $refund_fee * 100,
        ];


        // 生成签名
        $params['sign'] = $this->makeSign($params);

        // 请求API

        $url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';

        $result = $this->post($url, $this->toXml($params), true, $this->getCertPem());




        // 请求失败
        if (empty($result)) {
            return false;
        }
        // 格式化返回结果
        $prepay = $this->fromXml($result);


        // 请求失败
        if ($prepay['return_code'] === 'FAIL') {
            return false;
        }
        if ($prepay['result_code'] === 'FAIL') {
            return false;
        }
        return true;
    }


    /**
     * 加密
     * @param $url
     * @param $http_method
     * @param $body
     * @param $apiclient_key_url
     * @return string[]
     */
    public function getV3Sign($url, $http_method, $body, $apiclient_key_url)
    {
        $this->apimch_id = '1634712578';
        //商户号
        $mchid = $this->apimch_id;
        //随机字符串
        $nonce = strtoupper($this->createNoncestr());
        //商户序列号
        $serialNo = '7B28BE88E9B96D129C6E8A59359FC2B5107D6477';
        //时间戳
        $timestamp = time();
        //url
        $url_parts = parse_url($url);
        //获取绝对路径
        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        //密钥key
        $private_key = $this->getPrivateKey($apiclient_key_url);

        //拼接参数
        $message = $http_method . "\n" .
            $canonical_url . "\n" .
            $timestamp . "\n" .
            $nonce . "\n" .
            $body . "\n";
        //计算签名值
        openssl_sign($message, $raw_sign, $private_key, 'sha256WithRSAEncryption');

        $sign = base64_encode($raw_sign);

        $token = sprintf('WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
            $mchid, $nonce, $timestamp, $serialNo, $sign);
        $headers = [
            'Accept: application/json',
            'User-Agent: */*',
            'Content-Type: application/json; charset=utf-8',
            'Authorization: ' . $token,
        ];
        return $headers;
    }


    /**
     * 獲取密碼
     * @param $filepath
     * @return false|OpenSSLAsymmetricKey
     */
    public function getPrivateKey($filepath)
    {


        return openssl_get_privatekey(file_get_contents($filepath));
    }


    /**
     * 生成订单号
     * @return string
     */
    public function orderOn()
    {
        $order_id_main = date('YmdHis') . rand(10000000, 99999999);
        $order_id_len = strlen($order_id_main);
        $order_id_sum = 0;
        for ($i = 0; $i < $order_id_len; $i++) {
            $order_id_sum += (int)(substr($order_id_main, $i, 1));
        }
        $osn = $order_id_main . str_pad((100 - $order_id_sum % 100) % 100, 2, '0', STR_PAD_LEFT);

        return $osn;
    }


    /**
     * 返回商户退款订单号,商家自定义,此处使用日期+时间戳+4位随机数
     * @return string
     */
    public function getRefundOrder(): string
    {
        $basicData = '1234567890';
        str_shuffle($basicData);
        $disposeData = substr($basicData, 0, 4);
        return date('YmdHis') . time() . $disposeData;
    }


    public function refunds($transaction_id, $total_fee, $refund_fee)
    {
        // 当前时间
        $time = $transaction_id;
        // 生成随机字符串
        $nonceStr = md5($time . $transaction_id . $total_fee . $refund_fee);
        // API参数

        $apiclient_key_url = "./uploads/key/0apiclient_key.pem";
        $amount = array(
            'refund' => $refund_fee * 100,
            'total' => $total_fee * 100,
            'currency' => 'CNY',
        );
        $params = [
            'out_trade_no' => trim($transaction_id),
            'out_refund_no' => trim($time),
            'amount' => $amount,
            'sub_mchid' => trim($this->config['mchid'])
        ];
        // 请求API
        $inputData = json_encode($params);//v3微信传递参数需要json格式
        $headers = $this->getV3Sign('https://api.mch.weixin.qq.com/v3/refund/domestic/refunds', 'POST', $inputData, $apiclient_key_url);//将签名放在报头里
        $result = $this->post_data('https://api.mch.weixin.qq.com/v3/refund/domestic/refunds', $inputData, $headers);//下单

        $res = json_decode($result, true);

        if(isset($res['status'])){
            if ($res['status'] == "PROCESSING") {
                return true;
            }
        }

        return false;


    }

    /**
     * 請求
     * @param $url
     * @param $data
     * @param $headers
     * @return bool|string
     */
    public function post_data($url, $data = [], $headers = [])
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        //设置header头
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        // POST数据
        curl_setopt($ch, CURLOPT_POST, 1);
        // 把post的变量加上
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        $output = curl_exec($ch);

        curl_close($ch);
        return $output;
    }

    //作用：产生随机字符串，不长于32位
    private function createNoncestr($length = 32)
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    /**
     * 模拟POST请求
     * @param $url
     * @param array $data
     * @param bool $useCert
     * @param array $sslCert
     * @return mixed
     */
    protected function post($url, $data = [], $useCert = false, $sslCert = [])
    {


        $header = [
            'Content-type: application/json;'
        ];
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
        curl_setopt($curl, CURLOPT_HEADER, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_POST, TRUE);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);

        if ($useCert == true) {


            // 设置证书：cert 与 key 分别属于两个.pem文件
            curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM');
            curl_setopt($curl, CURLOPT_SSLCERT, $sslCert['certPem']);
            curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM');
            curl_setopt($curl, CURLOPT_SSLKEY, $sslCert['keyPem']);

        }

        $result = curl_exec($curl);
        curl_close($curl);
        return $result;
    }

    /**
     * 企业付款到零钱API
     * @param $order_no
     * @param $openid
     * @param $amount
     * @param $desc
     * @return bool
     */
    public function transfers($order_no, $openid, $amount, $desc)
    {
        // API参数
        $params = [
            'mch_appid' => $this->appId,
            'mchid' => $this->config['mchid'],
            'nonce_str' => md5(uniqid()),
            'partner_trade_no' => $order_no,
            'openid' => $openid,
            'check_name' => 'NO_CHECK',
            'amount' => $amount * 100,
            'desc' => $desc,
            'spbill_create_ip' => \request()->ip(),
        ];
        // 生成签名
        $params['sign'] = $this->makeSign($params);
        // 请求API
        $url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers';
        $result = $this->post($url, $this->toXml($params), true, $this->getCertPem());
        // 请求失败
        if (empty($result)) {
            return message('请求失败');
        }
        // 格式化返回结果
        $prepay = $this->fromXml($result);
        // 请求失败
        if ($prepay['return_code'] === 'FAIL') {
            return message($prepay['return_msg'], false);
        }
        if ($prepay['result_code'] === 'FAIL') {
            return message($prepay['err_code_des'], false);
        }
        return true;
    }

    /**
     * 获取cert证书文件
     * @return array
     * @throws BaseException
     */
    private function getCertPem()
    {
        if (empty($this->config['apiclient_url']) || empty($this->config['apiclient_key_url'])) {
            return message('请先到后台小程序设置填写微信支付证书文件');
        }

        // cert目录

        return [
            'certPem' => $this->config['apiclient_url'],
            'keyPem' => $this->config['apiclient_key_url']
        ];
    }

    /**
     * 实例化订单模型 (根据attach判断)
     * @param $orderNo
     * @param null $attach
     * @return mixed
     */
    private function getOrderModel($orderNo, $attach = null)
    {
        $attach = json_decode($attach, true);
        // 判断订单类型返回对应的订单模型
        $model = $this->modelClass[$attach['order_type']];
        return new $model($orderNo);
    }

    /**
     * 返回状态给微信服务器
     * @param boolean $returnCode
     * @param string $msg
     */
    private function returnCode($returnCode = true, $msg = null)
    {
        // 返回状态
        $return = [
            'return_code' => $returnCode ? 'SUCCESS' : 'FAIL',
            'return_msg' => $msg ?: 'OK',
        ];
        // 记录日志
        log_write([
            'describe' => '返回微信支付状态',
            'data' => $return
        ]);
        die($this->toXml($return));
    }

    /**
     * 生成paySign
     * @param $nonceStr
     * @param $prepay_id
     * @param $timeStamp
     * @return string
     */
    private function makePaySign($nonceStr, $prepay_id, $timeStamp)
    {
        $data = [
            'appId' => $this->appId,
            'nonceStr' => $nonceStr,
            'package' => 'prepay_id=' . $prepay_id,
            'signType' => 'MD5',
            'timeStamp' => $timeStamp,
        ];
        // 签名步骤一：按字典序排序参数
        ksort($data);
        $string = $this->toUrlParams($data);
        // 签名步骤二：在string后加入KEY
        $string = $string . '&key=' . $this->config['apikey'];
        // 签名步骤三：MD5加密
        $string = md5($string);
        // 签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * 将xml转为array
     * @param $xml
     * @return mixed
     */
    private function fromXml($xml)
    {
        // 禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    }

    /**
     * 生成签名
     * @param $values
     * @return string 本函数不覆盖sign成员变量，如要设置签名需要调用SetSign方法赋值
     */
    private function makeSign($values)
    {
        //签名步骤一：按字典序排序参数
        ksort($values);
        $string = $this->toUrlParams($values);
        //签名步骤二：在string后加入KEY
        $string = $string . '&key=' . $this->config['apikey'];
        //签名步骤三：MD5加密
        $string = md5($string);
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * 格式化参数格式化成url参数
     * @param $values
     * @return string
     */
    private function toUrlParams($values)
    {
        $buff = '';
        foreach ($values as $k => $v) {
            if ($k != 'sign' && $v != '' && !is_array($v)) {
                $buff .= $k . '=' . $v . '&';
            }
        }
        return trim($buff, '&');
    }

    /**
     * 输出xml字符
     * @param $values
     * @return bool|string
     */
    private function toXml($values)
    {
        if (!is_array($values)
            || count($values) <= 0
        ) {
            return false;
        }

        $xml = "<xml>";
        foreach ($values as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

}
